const THREE = window.THREE = require('three');
// const zlib = window.Zlib = require('zlib');
const Stats = require('../lib/stats.min');
const dat = require('dat.gui');
const environments = require('../assets/environment/index');
const createVignetteBackground = require('three-vignette-background');

// require('three/examples/js/loaders/GLTFLoader');
require('../lib/GLTFLoader');
require('../lib/FBXLoader');
require('../lib/OGLLoader');
// require('../lib/gunzip.min.js');
// require('../lib/inflate.min.js');
require('three/examples/js/loaders/OBJLoader');
require('three/examples/js/loaders/MTLLoader');
require('three/examples/js/loaders/DRACOLoader');
require('three/examples/js/loaders/DDSLoader');
require('three/examples/js/controls/OrbitControls');
require('three/examples/js/loaders/RGBELoader');
require('three/examples/js/loaders/HDRCubeTextureLoader');
require('three/examples/js/pmrem/PMREMGenerator');
require('three/examples/js/pmrem/PMREMCubeUVPacker');
require('three/examples/js/exporters/OBJExporter');
require('three/examples/js/exporters/GLTFExporter');

THREE.DRACOLoader.setDecoderPath('lib/draco/');

const DEFAULT_CAMERA = '[default]';

const IS_IOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;

// glTF texture types. `envMap` is deliberately omitted, as it's used internally
// by the loader but not part of the glTF format.
const MAP_NAMES = [
    'map',
    'aoMap',
    'emissiveMap',
    'glossinessMap',
    'metalnessMap',
    'normalMap',
    'roughnessMap',
    'specularMap',
];

const Preset = {ASSET_GENERATOR: 'assetgenerator'};

module.exports = class Viewer {

    constructor(el, options) {
        this.el = el;
        this.options = options;

        this.lights = [];
        this.content = null;
        this.mixer = null;
        this.clips = [];
        this.gui = null;

        this.state = {
            environment: options.preset === Preset.ASSET_GENERATOR
                ? 'Footprint Court (HDR)'
                : environments[1].name,
            background: false,
            playbackSpeed: 1.0,
            actionStates: {},
            camera: DEFAULT_CAMERA,
            wireframe: false,
            skeleton: false,
            grid: false,
			side: false,
			normals: false, 

            // Lights
            addLights: true,
            exposure: 1.0,
            textureEncoding: 'sRGB',
            ambientIntensity: 0.3,
            ambientColor: 0xFFFFFF,
            directIntensity: 0.8 * Math.PI, // TODO(#116)
            directColor: 0xFFFFFF,
            bgColor1: '#ffffff',
            bgColor2: '#353535'
        };

        this.prevTime = 0;

        this.renderInfo = {
            calls: 0,
            triangles: 0,
            vertices: 0,
            geometryMemory: "",
            textureMemory: "0kb"
        };

        this.stats = new Stats();
        this.stats.dom.height = '48px';
        [].forEach.call(this.stats.dom.children, (child) => (child.style.display = ''));

        this.scene = window.scene = new THREE.Scene();
        this.loadMesh;
        // this.scene1 = new THREE.Scene();
        // this.scene1.add(new THREE.AxesHelper(100));

        const fov = options.preset === Preset.ASSET_GENERATOR
            ? 0.8 * 180 / Math.PI
            : 60;
        this.defaultCamera = new THREE.PerspectiveCamera(fov, el.clientWidth / el.clientHeight, 0.01, 1000);
        this.activeCamera = this.defaultCamera;
        this.activeCamera.up.set(0, 0, 1); // axis Z is up
        this.scene.add(this.defaultCamera);

        // var axesHelper = new THREE.AxesHelper(2000);
        // let group = new THREE.Group();
        // group.add(axesHelper);
        // group.renderOrder = 999;//不知道什么原因和Babylon一样手动调整渲染顺序也好了
        // axesHelper.material.linewidth = 10;
        // this.scene.add(group);

        this.renderer = window.renderer = new THREE.WebGLRenderer({antialias: true});
        this.renderer.physicallyCorrectLights = true;
        this.renderer.gammaOutput = true;
        this.renderer.gammaFactor = 2.2;
        this.renderer.setClearColor(0xcccccc);
        this.renderer.setPixelRatio(window.devicePixelRatio);
        this.renderer.setSize(el.clientWidth, el.clientHeight);

        this.controls = new THREE.OrbitControls(this.defaultCamera, this.renderer.domElement);
        this.controls.autoRotate = false;
        this.controls.autoRotateSpeed = -10;

        this.controls.screenSpacePanning = true;

        this.background = createVignetteBackground({
            aspect: this.defaultCamera.aspect,
            grainScale: IS_IOS ? 0 : 0.001, // mattdesl/three-vignette-background#1
            colors: [this.state.bgColor1, this.state.bgColor2]
        });

        this.el.appendChild(this.renderer.domElement);

        this.cameraCtrl = null;
        this.cameraFolder = null;
        this.animFolder = null;
        this.animCtrls = [];
        this.morphFolder = null;
        this.morphCtrls = [];
        this.skeletonHelpers = [];
        this.gridHelper = null;
        this.axesHelper = null;

        this.addGUI();
        if (options.kiosk) this.gui.close();

        this.animate = this.animate.bind(this);
        requestAnimationFrame(this.animate);
        window.addEventListener('resize', this.resize.bind(this), false);
        this.totalVertexCount = 0;
        this.textureMemoryCtrl;
		
		this.normalHelpers = [];
    }


    calcTotalVertexCount(object3D) {
        if (object3D.geometry && object3D.geometry.attributes && object3D.geometry.attributes.position) {
            this.totalVertexCount = this.totalVertexCount + object3D.geometry.attributes.position.count;
        }

        if (object3D.children)
            object3D.children.forEach((obj3d) => this.calcTotalVertexCount(obj3d));
    }

    animate(time) {
        requestAnimationFrame(this.animate);
        const dt = (time - this.prevTime) / 1000;

        this.renderInfo.triangles = this.renderer.info.render.triangles;
        this.renderInfo.calls = this.renderer.info.render.calls;
        this.renderInfo.vertices = this.totalVertexCount;
        this.gui.updateDisplay();
        this.controls.update();
        this.stats.update();
        this.mixer && this.mixer.update(dt);
        this.render();

        this.prevTime = time;

    }

    render() {
		
		

        this.renderer.render(this.scene, this.activeCamera);
        // this.renderer.autoClear = false;
        // this.renderer.render(this.scene1, this.activeCamera);
        // this.renderer.autoClear = true;
    }

    resize() {

        const {clientHeight, clientWidth} = this.el.parentElement;

        this.defaultCamera.aspect = clientWidth / clientHeight;
        this.defaultCamera.updateProjectionMatrix();
        this.background.style({aspect: this.defaultCamera.aspect});
        this.renderer.setSize(clientWidth, clientHeight);

    }

    load(url, rootPath, assetMap, fileExt) {
		
		var scope = this;

        const baseURL = THREE.LoaderUtils.extractUrlBase(url);

        // Load.
        return new Promise((resolve, reject) => {

                const manager = new THREE.LoadingManager();

                // Intercept and override relative URLs.
                manager.setURLModifier((url, path) => {

                    const normalizedURL = rootPath + url
                        .replace(baseURL, '')
                        .replace(/^(\.?\/)/, '');

                    if (assetMap.has(normalizedURL)) {
                        const blob = assetMap.get(normalizedURL);
                        const blobURL = URL.createObjectURL(blob);
                        blobURLs.push(blobURL);
                        return blobURL;
                    }

                    return (path || '') + url;

                });

                let loader = new THREE.GLTFLoader(manager);
                // let loader = new THREE.OBJLoader2(manager);

                if (fileExt == "b3dm" || fileExt == "gltf" || fileExt == "glb") {
                    loader.setDRACOLoader(new THREE.DRACOLoader());
                    loader.setCrossOrigin('anonymous');
                }
                if (fileExt == "obj" || fileExt == "OBJ") {
                    let file = assetMap.values().next().value;
                    loader = new THREE.OBJLoader(manager);

                    if (file && file.materials)
                        loader.setMaterials(file.materials);
                }
                if (fileExt == "FBX"|| fileExt == "fbx") {
                    loader = new THREE.FBXLoader(manager);
                    loader.setCrossOrigin('anonymous');
                }
                if (fileExt == "ogl") {
                    loader = new THREE.OGLLoader(manager);
                }

                const blobURLs = [];

                loader.load(url, (result) => {
                    var scene;
                    if (result instanceof THREE.Group) {
                        scene = result;
                    } else {
                        scene = result.scene || result.scenes[0];
                    }

                    this.loadMesh = scene;
					
					scene.children.forEach(mesh => {
                        if (mesh instanceof THREE.Mesh && mesh.geometry.attributes.normal){
                            var vertexNormalsHelper = new THREE.VertexNormalsHelper( mesh, 10 );
                            vertexNormalsHelper.visible = false;
                            scope.normalHelpers.push(vertexNormalsHelper);
                        }
						
					});
					

                    const clips = result.animations || [];
                    this.setContent(scene, clips);

                    var count = 0;
                    var vertexCount = 0;

                    var sceneQueue = [];
                    sceneQueue.push(scene);

                    while (sceneQueue.length) {
                        let group = sceneQueue.pop();

                        for (let i = 0; i < group.children.length; i++) {
                            let mesh = group.children[i];
                            if (mesh.geometry) {
                                let attributes = mesh.geometry.attributes;
                                if (attributes.position) {
                                    count += attributes.position.count * 3 * 4;
                                    vertexCount += attributes.position.count;
                                }
                                attributes.normal && (count += attributes.normal.count * 3 * 4);
                                attributes.uv && (count += attributes.uv.count * 2 * 4);
                                mesh.geometry.index && (count += mesh.geometry.index.count * 4);
                            }
                            if (mesh.children && mesh.children.length !== 0) {
                                sceneQueue.push(mesh);
                            }
                        }
                    }

                    this.totalVertexCount = vertexCount;
                    this.renderInfo.geometryMemory = (count / 1024).toFixed(1) + "kb";

                    this.textureMemoryCtrl.onChange(() => {
                        var sceneQueue = [];
                        let textureSet = new Set();
                        let totalImageSize = 0;
                        sceneQueue.push(scene);
                        while (sceneQueue.length) {
                            let group = sceneQueue.pop();
                            for (let i = 0; i < group.children.length; i++) {
                                let mesh = group.children[i];

                                if (mesh.material) {
                                    var materials;
                                    mesh.material.length ? materials = mesh.material : materials = [mesh.material];
                                    for (let j = 0; j < materials.length; j++) {
                                        const material = materials[j];
                                        let map = material.map;
                                        if (map && map.image) {
                                            if (!textureSet.has(map.image.src)) {
                                                textureSet.add(map.image.src);
                                                var size = map.image.width * map.image.height * 3;
                                                totalImageSize += size;
                                            }
                                        }
                                    }
                                }
                                if (mesh.children && mesh.children.length) {
                                    sceneQueue.push(mesh)
                                }
                            }
                        }

                        this.renderInfo.textureMemory = (totalImageSize / 1024).toFixed(1) + "kb";
                    });

                    let interval = setInterval(() => {
                        var sceneQueue = [];
                        let textureSet = new Set();
                        let totalImageSize = 0;
                        sceneQueue.push(scene);
                        while (sceneQueue.length) {
                            let group = sceneQueue.pop();
                            for (let i = 0; i < group.children.length; i++) {
                                let mesh = group.children[i];

                                if (mesh.material) {
                                    var materials;
                                    mesh.material.length ? materials = mesh.material : materials = [mesh.material];
                                    for (let j = 0; j < materials.length; j++) {
                                        const material = materials[j];
                                        let map = material.map;
                                        if (map && map.image) {
                                            if (!textureSet.has(map.image.src)) {
                                                textureSet.add(map.image.src);
                                                var size = map.image.width * map.image.height * 3;
                                                totalImageSize += size;
                                            }
                                        }
                                    }
                                }
                                if (mesh.children && mesh.children.length) {
                                    sceneQueue.push(mesh)
                                }
                            }
                        }

                        let textureMemory = (totalImageSize / 1024).toFixed(1) + "kb";
                        if (this.renderInfo.textureMemory === textureMemory) {
                            clearInterval(interval)
                        } else {
                            this.renderInfo.textureMemory = (totalImageSize / 1024).toFixed(1) + "kb";
                        }

                    }, 5000);

                    blobURLs.forEach(URL.revokeObjectURL);

                    // See: https://github.com/google/draco/issues/349
                    // THREE.DRACOLoader.releaseDecoderModule();

                    resolve(result);

                    // this.calcTotalVertexCount(this.scene);
                }, undefined, reject);
            }
        )
            ;

    }

    /**
     * @param {THREE.Object3D} object
     * @param {Array<THREE.AnimationClip} clips
     */
    setContent(object, clips) {

        this.clear();

        object.updateMatrixWorld();
        const box = new THREE.Box3().setFromObject(object);
        const size = box.getSize(new THREE.Vector3()).length();
        const center = box.getCenter(new THREE.Vector3());

        this.controls.reset();

        object.position.x += (object.position.x - center.x);
        object.position.y += (object.position.y - center.y);
        object.position.z += (object.position.z - center.z);
        this.controls.maxDistance = size * 10;
        this.defaultCamera.near = 0.5;
        //this.defaultCamera.near = 0.0001;
        this.defaultCamera.far = size * 100;
        this.defaultCamera.updateProjectionMatrix();

        if (this.options.cameraPosition) {

            this.defaultCamera.position.fromArray(this.options.cameraPosition);
            this.defaultCamera.lookAt(new THREE.Vector3());

        } else {

            this.defaultCamera.position.copy(center);
            this.defaultCamera.position.x += size / 2.0;
            this.defaultCamera.position.y += size / 5.0;
            this.defaultCamera.position.z += size / 2.0;
            this.defaultCamera.lookAt(center);

        }

        this.setCamera(DEFAULT_CAMERA);

        this.controls.saveState();

        this.scene.add(object);
        this.content = object;

        this.state.addLights = true;
        this.content.traverse((node) => {
            if (node.isLight) {
                this.state.addLights = false;
            }
        });

        this.setClips(clips);

        this.updateLights();
        // this.updateGUI();和这个无关
        this.updateEnvironment();
        this.updateTextureEncoding();
        this.updateDisplay();

        window.content = this.content;
        console.info('[glTF Viewer] THREE.Scene exported as `window.content`.');
        this.printGraph(this.content);

    }

    printGraph(node) {

        console.group(' <' + node.type + '> ' + node.name);
        node.children.forEach((child) => this.printGraph(child));
        console.groupEnd();

    }

    /**
     * @param {Array<THREE.AnimationClip} clips
     */
    setClips(clips) {
        if (this.mixer) {
            this.mixer.stopAllAction();
            this.mixer.uncacheRoot(this.mixer.getRoot());
            this.mixer = null;
        }

        clips.forEach((clip) => {
            if (clip.validate()) clip.optimize();
        });

        this.clips = clips;
        if (!clips.length) return;

        this.mixer = new THREE.AnimationMixer(this.content);
    }

    playAllClips() {
        this.clips.forEach((clip) => {
            this.mixer.clipAction(clip).reset().play();
            this.state.actionStates[clip.name] = true;
        });
    }

    /**
     * @param {string} name
     */
    setCamera(name) {
        if (name === DEFAULT_CAMERA) {
            this.controls.enabled = true;
            this.activeCamera = this.defaultCamera;
        } else {
            this.controls.enabled = false;
            this.content.traverse((node) => {
                if (node.isCamera && node.name === name) {
                    this.activeCamera = node;
                }
            });
        }
    }

    updateTextureEncoding() {
        const encoding = this.state.textureEncoding === 'sRGB'
            ? THREE.sRGBEncoding
            : THREE.LinearEncoding;
        traverseMaterials(this.content, (material) => {
            if (material.map) material.map.encoding = encoding;
            if (material.emissiveMap) material.emissiveMap.encoding = encoding;
            if (material.map || material.emissiveMap) material.needsUpdate = true;
        });
    }

    updateLights() {
        const state = this.state;
        const lights = this.lights;

        if (state.addLights && !lights.length) {
            this.addLights();
        } else if (!state.addLights && lights.length) {
            this.removeLights();
        }

        this.renderer.toneMappingExposure = state.exposure;

        if (lights.length === 2) {
            lights[0].intensity = state.ambientIntensity;
            lights[0].color.setHex(state.ambientColor);
            lights[1].intensity = state.directIntensity;
            lights[1].color.setHex(state.directColor);
        }
    }

    addLights() {
        const state = this.state;

        if (this.options.preset === Preset.ASSET_GENERATOR) {
            const hemiLight = new THREE.HemisphereLight();
            hemiLight.name = 'hemi_light';
            this.scene.add(hemiLight);
            this.lights.push(hemiLight);
            return;
        }

        const light1 = new THREE.AmbientLight(state.ambientColor, state.ambientIntensity);
        light1.name = 'ambient_light';
        this.scene.add(light1);

        const light2 = new THREE.DirectionalLight(state.directColor, state.directIntensity);
        light2.position.set(0.5, 0, 0.866); // ~60º
        light2.name = 'main_light';
        this.scene.add(light2);

        this.lights.push(light1, light2);
    }

    removeLights() {

        this.lights.forEach((light) => light.parent.remove(light));
        this.lights.length = 0;

    }

    updateEnvironment() {

        const environment = environments.filter((entry) => entry.name === this.state.environment)[0];

        this.getCubeMapTexture(environment).then(({envMap, cubeMap}) => {

            if ((!envMap || !this.state.background) && this.activeCamera === this.defaultCamera) {
                // this.scene.add(this.background);
            } else {
                // this.scene.remove(this.background);
            }

            traverseMaterials(this.content, (material) => {
                if (material.isMeshStandardMaterial || material.isGLTFSpecularGlossinessMaterial) {
                    material.envMap = envMap;
                    material.needsUpdate = true;
                }
            });

            this.scene.background = this.state.background ? cubeMap : null;

        });

    }

    getCubeMapTexture(environment) {
        const {path, format} = environment;

        // no envmap
        if (!path) return Promise.resolve({envMap: null, cubeMap: null});

        const cubeMapURLs = [
            path + 'posx' + format, path + 'negx' + format,
            path + 'posy' + format, path + 'negy' + format,
            path + 'posz' + format, path + 'negz' + format
        ];

        // hdr
        if (format === '.hdr') {

            return new Promise((resolve) => {

                new THREE.HDRCubeTextureLoader().load(THREE.UnsignedByteType, cubeMapURLs, (hdrCubeMap) => {

                    var pmremGenerator = new THREE.PMREMGenerator(hdrCubeMap);
                    pmremGenerator.update(this.renderer);

                    var pmremCubeUVPacker = new THREE.PMREMCubeUVPacker(pmremGenerator.cubeLods);
                    pmremCubeUVPacker.update(this.renderer);

                    resolve({
                        envMap: pmremCubeUVPacker.CubeUVRenderTarget.texture,
                        cubeMap: hdrCubeMap
                    });

                });

            });

        }

        // standard
        const envMap = new THREE.CubeTextureLoader().load(cubeMapURLs);
        envMap.format = THREE.RGBFormat;
        return Promise.resolve({envMap, cubeMap: envMap});

    }

    updateDisplay() {
        if (this.skeletonHelpers.length) {
            this.skeletonHelpers.forEach((helper) => this.scene.remove(helper));
        }

        traverseMaterials(this.content, (material) => {
            material.wireframe = this.state.wireframe;
            if (this.state.side) {
				material.side = THREE.FrontSide;
			} else {
				material.side = THREE.DoubleSide;
			}
        });

        this.content.traverse((node) => {
            if (node.isMesh && node.skeleton && this.state.skeleton) {
                const helper = new THREE.SkeletonHelper(node.skeleton.bones[0].parent);
                helper.material.linewidth = 3;
                this.scene.add(helper);
                this.skeletonHelpers.push(helper);
            }
        });

        if (this.state.grid !== Boolean(this.gridHelper)) {
            if (this.state.grid) {
                this.gridHelper = new THREE.GridHelper();
                this.axesHelper = new THREE.AxesHelper(2000);
                this.axesHelper.renderOrder = 999;
                this.axesHelper.onBeforeRender = (renderer) => renderer.clearDepth();
                this.scene.add(this.gridHelper);
                this.scene.add(this.axesHelper);
            } else {
                this.scene.remove(this.gridHelper);
                this.scene.remove(this.axesHelper);
                this.gridHelper = null;
                this.axesHelper = null;
            }
        }
		
		if (this.state.normals){
			this.normalHelpers.forEach(a => a.visible = true);
		} else {
			this.normalHelpers.forEach(a => a.visible = false);
		}
    }

    updateBackground() {
        this.background.style({colors: [this.state.bgColor1, this.state.bgColor2]});
    }

    updateRender() {
        this.background.style({colors: [this.state.bgColor1, this.state.bgColor2]});
    }
	
    addGUI() {

        const gui = this.gui = new dat.GUI({autoPlace: false, width: 260, hideable: true});

        // Display controls.
        const dispFolder = gui.addFolder('Display');
        const envBackgroundCtrl = dispFolder.add(this.state, 'background');
        envBackgroundCtrl.onChange(() => this.updateEnvironment());
        const wireframeCtrl = dispFolder.add(this.state, 'wireframe');
        wireframeCtrl.onChange(() => this.updateDisplay());
        const skeletonCtrl = dispFolder.add(this.state, 'skeleton');
        skeletonCtrl.onChange(() => this.updateDisplay());
        const gridCtrl = dispFolder.add(this.state, 'grid');
        gridCtrl.onChange(() => this.updateDisplay());
		const materialSide = dispFolder.add(this.state, 'side');
        materialSide.onChange(() => this.updateDisplay());
		const normalHelperSwitch = dispFolder.add(this.state, 'normals');
        normalHelperSwitch.onChange(() => this.updateDisplay());

        //wy add calls
        const renderInfoFolder = gui.addFolder('RenderInfo', this.renderInfo);
        renderInfoFolder.add(this.renderInfo, 'calls');
        renderInfoFolder.add(this.renderInfo, 'triangles');
        renderInfoFolder.add(this.renderInfo, 'vertices');
        renderInfoFolder.add(this.renderInfo, 'geometryMemory');
        this.textureMemoryCtrl = renderInfoFolder.add(this.renderInfo, 'textureMemory');

        dispFolder.add(this.controls, 'autoRotate');
        dispFolder.add(this.controls, 'screenSpacePanning');
        const bgColor1Ctrl = dispFolder.addColor(this.state, 'bgColor1');
        const bgColor2Ctrl = dispFolder.addColor(this.state, 'bgColor2');
        bgColor1Ctrl.onChange(() => this.updateBackground());
        bgColor2Ctrl.onChange(() => this.updateBackground());

        // Lighting controls.
        const lightFolder = gui.addFolder('Lighting');
        const encodingCtrl = lightFolder.add(this.state, 'textureEncoding', ['sRGB', 'Linear']);
        encodingCtrl.onChange(() => this.updateTextureEncoding());
        lightFolder.add(this.renderer, 'gammaOutput').onChange(() => {
            traverseMaterials(this.content, (material) => {
                material.needsUpdate = true;
            });
        });
        const envMapCtrl = lightFolder.add(this.state, 'environment', environments.map((env) => env.name));
        envMapCtrl.onChange(() => this.updateEnvironment());
        [
            lightFolder.add(this.state, 'exposure', 0, 2),
            lightFolder.add(this.state, 'addLights').listen(),
            lightFolder.add(this.state, 'ambientIntensity', 0, 2),
            lightFolder.addColor(this.state, 'ambientColor'),
            lightFolder.add(this.state, 'directIntensity', 0, 4), // TODO(#116)
            lightFolder.addColor(this.state, 'directColor')
        ].forEach((ctrl) => ctrl.onChange(() => this.updateLights()));

        // Animation controls.
        this.animFolder = gui.addFolder('Animation');
        this.animFolder.domElement.style.display = 'none';
        const playbackSpeedCtrl = this.animFolder.add(this.state, 'playbackSpeed', 0, 1);
        playbackSpeedCtrl.onChange((speed) => {
            if (this.mixer) this.mixer.timeScale = speed;
        });
        this.animFolder.add({playAll: () => this.playAllClips()}, 'playAll');

        // Morph target controls.
        this.morphFolder = gui.addFolder('Morph Targets');
        this.morphFolder.domElement.style.display = 'none';

        // Camera controls.
        this.cameraFolder = gui.addFolder('Cameras');
        this.cameraFolder.domElement.style.display = 'none';

        // Stats.
        const perfFolder = gui.addFolder('Performance');
        const perfLi = document.createElement('li');
        this.stats.dom.style.position = 'static';
        perfLi.appendChild(this.stats.dom);
        perfLi.classList.add('gui-stats');
        perfFolder.__ul.appendChild(perfLi);

        const guiWrap = document.createElement('div');
        this.el.appendChild(guiWrap);
        guiWrap.classList.add('gui-wrap');
        guiWrap.appendChild(gui.domElement);
        gui.open();

    }

    updateGUI() {
        this.cameraFolder.domElement.style.display = 'none';

        this.morphCtrls.forEach((ctrl) => ctrl.remove());
        this.morphCtrls.length = 0;
        this.morphFolder.domElement.style.display = 'none';

        this.animCtrls.forEach((ctrl) => ctrl.remove());
        this.animCtrls.length = 0;
        this.animFolder.domElement.style.display = 'none';

        const cameraNames = [];
        const morphMeshes = [];
        this.content.traverse((node) => {
            if (node.isMesh && node.morphTargetInfluences) {
                morphMeshes.push(node);
            }
            if (node.isCamera) {
                node.name = node.name || `VIEWER__camera_${cameraNames.length + 1}`;
                cameraNames.push(node.name);
            }
        });

        if (cameraNames.length) {
            this.cameraFolder.domElement.style.display = '';
            if (this.cameraCtrl) this.cameraCtrl.remove();
            const cameraOptions = [DEFAULT_CAMERA].concat(cameraNames);
            this.cameraCtrl = this.cameraFolder.add(this.state, 'camera', cameraOptions);
            this.cameraCtrl.onChange((name) => this.setCamera(name));
        }

        if (morphMeshes.length) {
            this.morphFolder.domElement.style.display = '';
            morphMeshes.forEach((mesh) => {
                if (mesh.morphTargetInfluences.length) {
                    const nameCtrl = this.morphFolder.add({name: mesh.name || 'Untitled'}, 'name');
                    this.morphCtrls.push(nameCtrl);
                }
                for (let i = 0; i < mesh.morphTargetInfluences.length; i++) {
                    const ctrl = this.morphFolder.add(mesh.morphTargetInfluences, i, 0, 1, 0.01).listen();
                    Object.keys(mesh.morphTargetDictionary).forEach((key) => {
                        if (key && mesh.morphTargetDictionary[key] === i) ctrl.name(key);
                    });
                    this.morphCtrls.push(ctrl);
                }
            });
        }

        if (this.clips.length) {
            this.animFolder.domElement.style.display = '';
            const actionStates = this.state.actionStates = {};
            this.clips.forEach((clip, clipIndex) => {
                // Autoplay the first clip.
                let action;
                if (clipIndex === 0) {
                    actionStates[clip.name] = true;
                    action = this.mixer.clipAction(clip);
                    action.play();
                } else {
                    actionStates[clip.name] = false;
                }

                // Play other clips when enabled.
                const ctrl = this.animFolder.add(actionStates, clip.name).listen();
                ctrl.onChange((playAnimation) => {
                    action = action || this.mixer.clipAction(clip);
                    action.setEffectiveTimeScale(1);
                    playAnimation ? action.play() : action.stop();
                });
                this.animCtrls.push(ctrl);
            });
        }
    }

    clear() {

        // if (!this.content) return;

        // this.scene.remove(this.content);

        // // dispose geometry
        // this.content.traverse((node) => {

        //     if (!node.isMesh) return;

        //     node.geometry.dispose();

        // });

        // // dispose textures
        // traverseMaterials(this.content, (material) => {

        //     MAP_NAMES.forEach((map) => {

        //         if (material[map]) material[map].dispose();

        //     });

        // });

    }

    objExport() {
        var exporter = new THREE.OBJExporter();
        var result = exporter.parse(this.loadMesh);

        var MIME_TYPE = 'text/plain';
        window.URL = window.webkitURL || window.URL;

        var bb = new Blob([result], {type: MIME_TYPE});
        var a = document.createElement('a');
        a.style.display = 'none';
        a.download = 'export.obj';
        a.href = window.URL.createObjectURL(bb);
        a.textContent = 'Download ready';
        a.dataset.downloadurl = [MIME_TYPE, a.download, a.href].join(':');
        a.click();

    }


    /**
     *
     *
     * @author Yao Yuan
     * @date 2020-03-20
     */
    glbExport() {
        var exporter = new THREE.GLTFExporter();
        
        exporter.parse(this.loadMesh, function (result) {
            var MIME_TYPE = 'application/octet-stream';
            window.URL = window.webkitURL || window.URL;

            var bb = new Blob([result], {type: MIME_TYPE});
            var a = document.createElement('a');
            a.style.display = 'none';
            a.download = 'export.glb';
            a.href = window.URL.createObjectURL(bb);
            a.textContent = 'Download ready';
            a.dataset.downloadurl = [MIME_TYPE, a.download, a.href].join(':');
            a.click();
        }, {
            binary: true
        });

        
    }

}
;

function traverseMaterials(object, callback) {
    object.traverse((node) => {
        if (!node.isMesh) return;
        const materials = Array.isArray(node.material)
            ? node.material
            : [node.material];
        materials.forEach(callback);
    });
}
